use CLDRPluralRuleParser\Evaluator;
use CLDRPluralRuleParser\Error as CLDRPluralRuleError;
-use MediaWiki\Logger\LoggerFactory;
-use MediaWiki\MediaWikiServices;
+use MediaWiki\Config\ServiceOptions;
+use MediaWiki\Languages\LanguageNameUtils;
+use Psr\Log\LoggerInterface;
/**
* Class for caching the contents of localisation files, Messages*.php
* and *.i18n.php.
*
- * An instance of this class is available using Language::getLocalisationCache().
+ * An instance of this class is available using MediaWikiServices.
*
* The values retrieved from here are merged, containing items from extension
* files, core messages files and the language fallback sequence (e.g. zh-cn ->
class LocalisationCache {
const VERSION = 4;
- /** Configuration associative array */
- private $conf;
+ /** @var ServiceOptions */
+ private $options;
/**
* True if recaching should only be done on an explicit call to recache().
*/
private $manualRecache = false;
- /**
- * True to treat all files as expired until they are regenerated by this object.
- */
- private $forceRecache = false;
-
/**
* The cache data. 3-d array, where the first key is the language code,
* the second key is the item key e.g. 'messages', and the third key is
private $store;
/**
- * @var \Psr\Log\LoggerInterface
+ * @var LoggerInterface
*/
private $logger;
+ /** @var callable[] See comment for parameter in constructor */
+ private $clearStoreCallbacks;
+
+ /** @var LanguageNameUtils */
+ private $langNameUtils;
+
/**
* A 2-d associative array, code/key, where presence indicates that the item
* is loaded. Value arbitrary.
private $mergeableKeys = null;
+ /**
+ * @todo Make this a const when HHVM support is dropped (T192166)
+ *
+ * @var array
+ * @since 1.34
+ */
+ public static $constructorOptions = [
+ // True to treat all files as expired until they are regenerated by this object.
+ 'forceRecache',
+ 'manualRecache',
+ 'ExtensionMessagesFiles',
+ 'MessagesDirs',
+ ];
+
/**
* For constructor parameters, see the documentation in DefaultSettings.php
* for $wgLocalisationCacheConf.
*
- * @param array $conf
+ * Do not construct this directly. Use MediaWikiServices.
+ *
+ * @param ServiceOptions $options
+ * @param LCStore $store What backend to use for storage
+ * @param LoggerInterface $logger
+ * @param callable[] $clearStoreCallbacks To be called whenever the cache is cleared. Can be
+ * used to clear other caches that depend on this one, such as ResourceLoader's
+ * MessageBlobStore.
+ * @param LanguageNameUtils $langNameUtils
* @throws MWException
*/
- function __construct( $conf ) {
- global $wgCacheDirectory;
-
- $this->conf = $conf;
- $this->logger = LoggerFactory::getInstance( 'localisation' );
-
- $directory = !empty( $conf['storeDirectory'] ) ? $conf['storeDirectory'] : $wgCacheDirectory;
- $storeArg = [];
- $storeArg['directory'] = $directory;
-
- if ( !empty( $conf['storeClass'] ) ) {
- $storeClass = $conf['storeClass'];
- } else {
- switch ( $conf['store'] ) {
- case 'files':
- case 'file':
- $storeClass = LCStoreCDB::class;
- break;
- case 'db':
- $storeClass = LCStoreDB::class;
- $storeArg['server'] = $conf['storeServer'] ?? [];
- break;
- case 'array':
- $storeClass = LCStoreStaticArray::class;
- break;
- case 'detect':
- if ( $directory ) {
- $storeClass = LCStoreCDB::class;
- } else {
- $storeClass = LCStoreDB::class;
- $storeArg['server'] = $conf['storeServer'] ?? [];
- }
- break;
- default:
- throw new MWException(
- 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
- );
- }
- }
- $this->logger->debug( static::class . ": using store $storeClass" );
-
- $this->store = new $storeClass( $storeArg );
- foreach ( [ 'manualRecache', 'forceRecache' ] as $var ) {
- if ( isset( $conf[$var] ) ) {
- $this->$var = $conf[$var];
- }
- }
+ function __construct(
+ ServiceOptions $options,
+ LCStore $store,
+ LoggerInterface $logger,
+ array $clearStoreCallbacks,
+ LanguageNameUtils $langNameUtils
+ ) {
+ $options->assertRequiredOptions( self::$constructorOptions );
+
+ $this->options = $options;
+ $this->store = $store;
+ $this->logger = $logger;
+ $this->clearStoreCallbacks = $clearStoreCallbacks;
+ $this->langNameUtils = $langNameUtils;
+
+ // Keep this separate from $this->options so it can be mutable
+ $this->manualRecache = $options->get( 'manualRecache' );
}
/**
* @return bool
*/
public function isExpired( $code ) {
- if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
- $this->logger->debug( __METHOD__ . "($code): forced reload\n" );
+ if ( $this->options->get( 'forceRecache' ) && !isset( $this->recachedLangs[$code] ) ) {
+ $this->logger->debug( __METHOD__ . "($code): forced reload" );
return true;
}
$preload = $this->store->get( $code, 'preload' );
// Different keys may expire separately for some stores
if ( $deps === null || $keys === null || $preload === null ) {
- $this->logger->debug( __METHOD__ . "($code): cache missing, need to make one\n" );
+ $this->logger->debug( __METHOD__ . "($code): cache missing, need to make one" );
return true;
}
// When this happens, always expire the cache
if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
$this->logger->debug( __METHOD__ . "($code): cache for $code expired due to " .
- get_class( $dep ) . "\n" );
+ get_class( $dep ) );
return true;
}
$this->initialisedLangs[$code] = true;
# If the code is of the wrong form for a Messages*.php file, do a shallow fallback
- if ( !Language::isValidBuiltInCode( $code ) ) {
+ if ( !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
$this->initShallowFallback( $code, 'en' );
return;
# Recache the data if necessary
if ( !$this->manualRecache && $this->isExpired( $code ) ) {
- if ( Language::isSupportedLanguage( $code ) ) {
+ if ( $this->langNameUtils->isSupportedLanguage( $code ) ) {
$this->recache( $code );
} elseif ( $code === 'en' ) {
throw new MWException( 'MessagesEn.php is missing.' );
global $IP;
// This reads in the PHP i18n file with non-messages l10n data
- $fileName = Language::getMessagesFileName( $code );
+ $fileName = $this->langNameUtils->getMessagesFileName( $code );
if ( !file_exists( $fileName ) ) {
$data = [];
} else {
public function getMessagesDirs() {
global $IP;
- $config = MediaWikiServices::getInstance()->getMainConfig();
- $messagesDirs = $config->get( 'MessagesDirs' );
return [
'core' => "$IP/languages/i18n",
'exif' => "$IP/languages/i18n/exif",
'api' => "$IP/includes/api/i18n",
'oojs-ui' => "$IP/resources/lib/ooui/i18n",
- ] + $messagesDirs;
+ ] + $this->options->get( 'MessagesDirs' );
}
/**
* @throws MWException
*/
public function recache( $code ) {
- global $wgExtensionMessagesFiles;
-
if ( !$code ) {
throw new MWException( "Invalid language code requested" );
}
# Load the primary localisation from the source file
$data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
if ( $data === false ) {
- $this->logger->debug( __METHOD__ . ": no localisation file for $code, using fallback to en\n" );
+ $this->logger->debug( __METHOD__ . ": no localisation file for $code, using fallback to en" );
$coreData['fallback'] = 'en';
} else {
- $this->logger->debug( __METHOD__ . ": got localisation for $code from source\n" );
+ $this->logger->debug( __METHOD__ . ": got localisation for $code from source" );
# Merge primary localisation
foreach ( $data as $key => $value ) {
# Load non-JSON localisation data for extensions
$extensionData = array_fill_keys( $codeSequence, $initialData );
- foreach ( $wgExtensionMessagesFiles as $extension => $fileName ) {
+ foreach ( $this->options->get( 'ExtensionMessagesFiles' ) as $extension => $fileName ) {
if ( isset( $messageDirs[$extension] ) ) {
# This extension has JSON message data; skip the PHP shim
continue;
# HACK: If using a null (i.e. disabled) storage backend, we
# can't write to the MessageBlobStore either
if ( !$this->store instanceof LCStoreNull ) {
- $blobStore = MediaWikiServices::getInstance()->getResourceLoader()->getMessageBlobStore();
- $blobStore->clear();
+ foreach ( $this->clearStoreCallbacks as $callback ) {
+ $callback();
+ }
}
}
$this->store = new LCStoreNull;
$this->manualRecache = false;
}
-
}